
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class WebServer {

    private static final String CONTENT_TYPE = "Content-Type";
    private static final String CONTENT_LENGTH = "Content-Length";

    public static void main(String args[]) throws Exception {
        //get the port number from command line
        int portNo = Integer.parseInt(args[0]);
        ServerSocket serverSock = new ServerSocket(portNo);

        while (true) {
            Socket s = serverSock.accept();

            //create a reader to read data input to this socket
            InputStream is = s.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            //create a output stream to write data ouput to the client
            OutputStream os = s.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);

            //split http request header
            String input = br.readLine();
            String fields[] = input.split(" ");

            //get the file name
            String headerInput[] = fields[1].split("\\?");
            String fileName = headerInput[0];
            //getRequestHeader() will Separate values and keys in the request header and store them into a map
            Map<String, String> requestHeader = getRequestHeader(br);

            //for GET requests
            if (fields[0].equals("GET")) {
                echo("receive GET request.");
                //to get the query string if it has one
                String queryString = headerInput.length > 1 ? headerInput[1] : "NULL";
                echo("File name: " + fileName + " Query String: " + queryString);

                //GET request for perl file without query string
                if (fileName.endsWith(".pl")) {
                    echo("Request for .pl file:");

                    Map<String, String> environmentVariables = new HashMap<String, String>();
                    environmentVariables.put("REQUEST_METHOD", "GET");
                    environmentVariables.put("QUERY_STRING", queryString);
                    //execute the .pl file with proper environment variables
                    Process p = executeScript(environmentVariables, fileName);
                    //write the output to the client
                    transmitCommandLineOutput(dos, p);

                    //request for .css or .gif file
                } else if (fields[1].endsWith(".css") || fields[1].endsWith(".gif")) {
                    echo("Request for non .pl file..");
                    String realFileName = fileName.startsWith("/") ? fileName.substring(1) : fileName;
                    File f = new File(realFileName);

                    if (f.exists() && f.canRead()) {
                        transmitFile(dos, realFileName, f);
                    } else {
                        echo("File " + realFileName + " does not exist.");
                        dos.writeBytes("HTTP/1.0 404 Not Found\r\n");
                        dos.writeBytes("\r\n");
                        dos.writeBytes("Cannot find " + realFileName);
                    }
                } else {
                    echo("Unkonwn get request (non .css .gif)" + fileName + "?" + queryString);
                }
                echo("End of GET request processing");
                echo("");

                //for POST requests
            } else if (fields[0].equals("POST")) {
                if (fields[0].equals("POST")) {
                    echo("received POST request");
                    if (requestHeader.containsKey(CONTENT_TYPE)) {
                        String contentType = requestHeader.get(CONTENT_TYPE);
                        int contentLength = Integer.parseInt(requestHeader.get(CONTENT_LENGTH));
                        char[] queryChars = new char[contentLength];
                        br.read(queryChars, 0, contentLength);
                        String queryString = new String(queryChars);

                        if (contentType.equals("application/x-www-form-urlencoded") || contentType.contains("multipart/form-data")) {
                            Map<String, String> environmentVariables = new HashMap<String, String>();
                            environmentVariables.put("REQUEST_METHOD", "POST");
                            environmentVariables.put("QUERY_STRING",
                                    contentType.equals("application/x-www-form-urlencoded") ? queryString : "NULL");
                            environmentVariables.put("CONTENT_LENGTH", requestHeader.get(CONTENT_LENGTH));
                            environmentVariables.put("CONTENT_TYPE", contentType);
                            Process p = executeScript(environmentVariables, fileName);

                            //write the query stream or request body to the .pl file
                            DataOutputStream dataOutputStream = new DataOutputStream(p.getOutputStream());
                            dataOutputStream.writeBytes(queryString);
                            dataOutputStream.flush();

                            transmitCommandLineOutput(dos, p);
                        } else {
                            echo("Unknown POST request. Content Type: " + contentType);
                        }
                    }
                }
                echo("End of POST request processing");
                echo("");
            } else {
                echo("Request header cannot be recognized");
            }
            s.close();
        }
    }

    //to transmit a .css or .gif file to the client
    private static void transmitFile(DataOutputStream dos, String fileName, File f) throws IOException {
        int fileSize = (int) f.length();
        echo("Reading file: " + fileName + " size: " + fileSize);

        FileInputStream fileInputStream = new FileInputStream(fileName);
        byte[] buffer = new byte[fileSize];
        fileInputStream.read(buffer);

        dos.writeBytes("HTTP/1.0 200 OK\r\n");
        if (fileName.endsWith("css")) {
            dos.writeBytes("Content-type: text/css\r\n");
        } else if (fileName.endsWith("gif")) {
            dos.writeBytes("Content-type: image/gif\r\n");
        }
        echo("Write " + fileName + " header done...");
        dos.writeBytes("\r\n");
        dos.write(buffer, 0, fileSize);
    }

    //write the output from the .pl file to the client
    private static void transmitCommandLineOutput(DataOutputStream dos, Process p) throws IOException {
        DataInputStream dataInputStream = new DataInputStream(p.getInputStream());
        InputStreamReader inputStreamReader = new InputStreamReader(dataInputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        StringBuffer buffer = new StringBuffer();
        buffer.append("HTTP/1.0 200 OK\r\n");
        buffer.append(bufferedReader.readLine() + "\n\n");
        bufferedReader.readLine();
        String perlOutput;
        do {
            perlOutput = bufferedReader.readLine();
            if (perlOutput != null) {
                echo(perlOutput);
                buffer.append(perlOutput);
                buffer.append("\n");
            }
        } while (perlOutput != null);
        dos.writeBytes(buffer.toString());
        dos.writeBytes("\r\n");
    }

    //to execute the script with proper environment variables
    private static Process executeScript(Map<String, String> params, String fileName) throws IOException {
        StringBuilder command = new StringBuilder("/usr/bin/env ");
        for (Map.Entry<String, String> pair : params.entrySet()) {
            command.append(String.format("%s=%s ", pair.getKey(), pair.getValue()));
        }
        command.append(String.format("/usr/bin/perl ../a1%s", fileName));
        return Runtime.getRuntime().exec(command.toString());
    }

    //to seperate keys and their repective values in the request header and store them into a map
    private static Map<String, String> getRequestHeader(BufferedReader br) throws IOException {
        Map<String, String> requestHeaders = new HashMap<String, String>();
        String input;
        do {
            input = br.readLine();
            System.out.println(input);
            if ((input != null || !input.equals("")) && input.contains(": ")) {
                String pair[] = {"", ""};
                pair = input.split(": ", 2);
                requestHeaders.put(pair[0], pair[1]);
            }
        } while (input.compareTo("") != 0);
        return requestHeaders;
    }

    //to print a string
    private static void echo(String input) {
        System.out.println(input);
    }
}
